/***********************************************************************
*
* Copyright (c) 2012-2025 Barbara Geller
* Copyright (c) 2012-2025 Ansel Sermersheim
*
* Copyright (c) 2015 The Qt Company Ltd.
* Copyright (c) 2012-2016 Digia Plc and/or its subsidiary(-ies).
* Copyright (c) 2008-2012 Nokia Corporation and/or its subsidiary(-ies).
*
* This file is part of CopperSpice.
*
* CopperSpice is free software. You can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* CopperSpice is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* https://www.gnu.org/licenses/
*
***********************************************************************/

#include <qsvggenerator.h>

#ifndef QT_NO_SVGGENERATOR

#include <qbuffer.h>
#include <qdebug.h>
#include <qfile.h>
#include <qmath.h>
#include <qpainterpath.h>
#include <qtextcodec.h>
#include <qtextstream.h>

#include <qdrawhelper_p.h>
#include <qpaintengine_p.h>
#include <qtextengine_p.h>

static void translate_color(const QColor &color, QString *color_string, QString *opacity_string)
{
   Q_ASSERT(color_string);
   Q_ASSERT(opacity_string);

   *color_string =
      QString::fromLatin1("#%1%2%3")
      .formatArg(color.red(), 2, 16, QLatin1Char('0'))
      .formatArg(color.green(), 2, 16, QLatin1Char('0'))
      .formatArg(color.blue(), 2, 16, QLatin1Char('0'));
   *opacity_string = QString::number(color.alphaF());
}

static void translate_dashPattern(QVector<qreal> pattern, const qreal &width, QString *pattern_string)
{
   Q_ASSERT(pattern_string);

   // Note that SVG operates in absolute lengths, whereas Qt uses a length/width ratio.
   for (qreal entry : pattern) {
      *pattern_string += QString("%1,").formatArg(entry * width);
   }

   pattern_string->chop(1);
}

class QSvgPaintEnginePrivate : public QPaintEnginePrivate
{
 public:
   QSvgPaintEnginePrivate() {
      size    = QSize();
      viewBox = QRectF();

      outputDevice = nullptr;
      resolution   = 72;

      attributes.document_title = "CopperSpice Svg Document";
      attributes.document_description = "Generated by CopperSpice";
      attributes.font_family  = QLatin1String("serif");
      attributes.font_size    = QLatin1String("10pt");
      attributes.font_style   = QLatin1String("normal");
      attributes.font_weight  = QLatin1String("normal");

      afterFirstUpdate = false;
      numGradients = 0;
   }

   QSize size;
   QRectF viewBox;
   QIODevice *outputDevice;
   QTextStream *stream;
   int resolution;

   QString header;
   QString defs;
   QString body;
   bool    afterFirstUpdate;

   QBrush brush;
   QPen pen;
   QMatrix matrix;
   QFont font;

   QString generateGradientName() {
      ++numGradients;
      currentGradientName = QString("gradient%1").formatArg(numGradients);
      return currentGradientName;
   }

   QString currentGradientName;
   int numGradients;

   struct _attributes {
      QString document_title;
      QString document_description;
      QString font_weight;
      QString font_size;
      QString font_family;
      QString font_style;
      QString stroke, strokeOpacity;
      QString dashPattern, dashOffset;
      QString fill, fillOpacity;
   } attributes;
};

static inline QPaintEngine::PaintEngineFeatures svgEngineFeatures()
{
   return QPaintEngine::PaintEngineFeatures(
             QPaintEngine::AllFeatures
             & ~QPaintEngine::PatternBrush
             & ~QPaintEngine::PerspectiveTransform
             & ~QPaintEngine::ConicalGradientFill
             & ~QPaintEngine::PorterDuff);
}

class QSvgPaintEngine : public QPaintEngine
{
   Q_DECLARE_PRIVATE(QSvgPaintEngine)

 public:
   QSvgPaintEngine()
      : QPaintEngine(*new QSvgPaintEnginePrivate, svgEngineFeatures()) { }

   bool begin(QPaintDevice *device) override;
   bool end() override;

   void updateState(const QPaintEngineState &state) override;
   void popGroup();

   void drawEllipse(const QRectF &r) override;
   void drawPath(const QPainterPath &path) override;
   void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) override;
   void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) override;
   void drawRects(const QRectF *rects, int rectCount) override;

   void drawTextItem(const QPointF &pt, const QTextItem &item) override;

   void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr,
                  Qt::ImageConversionFlags flags = Qt::AutoColor) override;

   QPaintEngine::Type type() const override {
      return QPaintEngine::SVG;
   }

   QSize size() const {
      return d_func()->size;
   }
   void setSize(const QSize &size) {
      Q_ASSERT(!isActive());
      d_func()->size = size;
   }

   QRectF viewBox() const {
      return d_func()->viewBox;
   }
   void setViewBox(const QRectF &viewBox) {
      Q_ASSERT(!isActive());
      d_func()->viewBox = viewBox;
   }

   QString documentTitle() const {
      return d_func()->attributes.document_title;
   }
   void setDocumentTitle(const QString &title) {
      d_func()->attributes.document_title = title;
   }

   QString documentDescription() const {
      return d_func()->attributes.document_description;
   }
   void setDocumentDescription(const QString &description) {
      d_func()->attributes.document_description = description;
   }

   QIODevice *outputDevice() const {
      return d_func()->outputDevice;
   }
   void setOutputDevice(QIODevice *device) {
      Q_ASSERT(!isActive());
      d_func()->outputDevice = device;
   }

   int resolution() {
      return d_func()->resolution;
   }
   void setResolution(int resolution) {
      Q_ASSERT(!isActive());
      d_func()->resolution = resolution;
   }
   void saveLinearGradientBrush(const QGradient *g) {
      QTextStream str(&d_func()->defs, QIODevice::Append);
      const QLinearGradient *grad = static_cast<const QLinearGradient *>(g);
      str << QLatin1String("<linearGradient ");
      saveGradientUnits(str, g);
      if (grad) {
         str << QLatin1String("x1=\"") << grad->start().x() << QLatin1String("\" ")
             << QLatin1String("y1=\"") << grad->start().y() << QLatin1String("\" ")
             << QLatin1String("x2=\"") << grad->finalStop().x() << QLatin1String("\" ")
             << QLatin1String("y2=\"") << grad->finalStop().y() << QLatin1String("\" ");
      }

      str << QLatin1String("id=\"") << d_func()->generateGradientName() << QLatin1String("\">\n");
      saveGradientStops(str, g);
      str << QLatin1String("</linearGradient>") << endl;
   }

   void saveRadialGradientBrush(const QGradient *g) {
      QTextStream str(&d_func()->defs, QIODevice::Append);
      const QRadialGradient *grad = static_cast<const QRadialGradient *>(g);
      str << QLatin1String("<radialGradient ");
      saveGradientUnits(str, g);

      if (grad) {
         str << QLatin1String("cx=\"") << grad->center().x() << QLatin1String("\" ")
             << QLatin1String("cy=\"") << grad->center().y() << QLatin1String("\" ")
             << QLatin1String("r=\"") << grad->radius() << QLatin1String("\" ")
             << QLatin1String("fx=\"") << grad->focalPoint().x() << QLatin1String("\" ")
             << QLatin1String("fy=\"") << grad->focalPoint().y() << QLatin1String("\" ");
      }

      str << QLatin1String("xml:id=\"") << d_func()->generateGradientName() << QLatin1String("\">\n");
      saveGradientStops(str, g);
      str << QLatin1String("</radialGradient>") << endl;
   }

   void saveConicalGradientBrush(const QGradient *) {
      qWarning("QSvgPaintEngine::saveConicalGradientBrush() Conical gradients are not supported");
   }

   void saveGradientStops(QTextStream &str, const QGradient *g) {
      QVector<QPair<qreal, QColor>> stops = g->stops();

      if (g->interpolationMode() == QGradient::ColorInterpolation) {
         bool constantAlpha = true;
         int alpha = stops.at(0).second.alpha();
         for (int i = 1; i < stops.size(); ++i) {
            constantAlpha &= (stops.at(i).second.alpha() == alpha);
         }

         if (!constantAlpha) {
            const qreal spacing = qreal(0.02);
            QVector<QPair<qreal, QColor>> newStops;
            QRgb fromColor = qPremultiply(stops.at(0).second.rgba());
            QRgb toColor;

            for (int i = 0; i + 1 < stops.size(); ++i) {
               int parts = qCeil((stops.at(i + 1).first - stops.at(i).first) / spacing);
               newStops.append(stops.at(i));
               toColor = qPremultiply(stops.at(i + 1).second.rgba());

               if (parts > 1) {
                  qreal step = (stops.at(i + 1).first - stops.at(i).first) / parts;
                  for (int j = 1; j < parts; ++j) {
                     QRgb color = qUnpremultiply(INTERPOLATE_PIXEL_256(fromColor, 256 - 256 * j / parts, toColor, 256 * j / parts));
                     newStops.append(QPair<qreal, QColor>(stops.at(i).first + j * step, QColor::fromRgba(color)));
                  }
               }
               fromColor = toColor;
            }

            newStops.append(stops.back());
            stops = newStops;
         }
      }

      for (auto stop : stops) {
         QString color =
            QString("#%1%2%3")
            .formatArg(stop.second.red(), 2, 16,   QLatin1Char('0'))
            .formatArg(stop.second.green(), 2, 16, QLatin1Char('0'))
            .formatArg(stop.second.blue(), 2, 16,  QLatin1Char('0'));

         str << "    <stop offset=\"" << stop.first << "\" "
             << "stop-color=\"" << color << "\" "
             << "stop-opacity=\"" << stop.second.alphaF() << "\" />\n";
      }
   }

   void saveGradientUnits(QTextStream &str, const QGradient *gradient) {
      str << QLatin1String("gradientUnits=\"");
      if (gradient && gradient->coordinateMode() == QGradient::ObjectBoundingMode) {
         str << QLatin1String("objectBoundingBox");
      } else {
         str << QLatin1String("userSpaceOnUse");
      }
      str << QLatin1String("\" ");
   }

   void generateQtDefaults() {
      *d_func()->stream << QLatin1String("fill=\"none\" ");
      *d_func()->stream << QLatin1String("stroke=\"black\" ");
      *d_func()->stream << QLatin1String("stroke-width=\"1\" ");
      *d_func()->stream << QLatin1String("fill-rule=\"evenodd\" ");
      *d_func()->stream << QLatin1String("stroke-linecap=\"square\" ");
      *d_func()->stream << QLatin1String("stroke-linejoin=\"bevel\" ");
      *d_func()->stream << QLatin1String(">\n");
   }

   QTextStream &stream() {
      return *d_func()->stream;
   }

   void qpenToSvg(const QPen &spen) {
      QString width;

      d_func()->pen = spen;

      switch (spen.style()) {
         case Qt::NoPen:
            stream() << QLatin1String("stroke=\"none\" ");

            d_func()->attributes.stroke = QLatin1String("none");
            d_func()->attributes.strokeOpacity = QString();
            return;
            break;
         case Qt::SolidLine: {
            QString color, colorOpacity;

            translate_color(spen.color(), &color,
                            &colorOpacity);
            d_func()->attributes.stroke = color;
            d_func()->attributes.strokeOpacity = colorOpacity;

            stream() << QLatin1String("stroke=\"") << color << QLatin1String("\" ");
            stream() << QLatin1String("stroke-opacity=\"") << colorOpacity << QLatin1String("\" ");
         }
         break;
         case Qt::DashLine:
         case Qt::DotLine:
         case Qt::DashDotLine:
         case Qt::DashDotDotLine:
         case Qt::CustomDashLine: {
            QString color, colorOpacity, dashPattern, dashOffset;

            qreal penWidth = spen.width() == 0 ? qreal(1) : spen.widthF();

            translate_color(spen.color(), &color, &colorOpacity);
            translate_dashPattern(spen.dashPattern(), penWidth, &dashPattern);

            // SVG uses absolute offset
            dashOffset = QString::number(spen.dashOffset() * penWidth);

            d_func()->attributes.stroke = color;
            d_func()->attributes.strokeOpacity = colorOpacity;
            d_func()->attributes.dashPattern = dashPattern;
            d_func()->attributes.dashOffset = dashOffset;

            stream() << QLatin1String("stroke=\"") << color << QLatin1String("\" ");
            stream() << QLatin1String("stroke-opacity=\"") << colorOpacity << QLatin1String("\" ");
            stream() << QLatin1String("stroke-dasharray=\"") << dashPattern << QLatin1String("\" ");
            stream() << QLatin1String("stroke-dashoffset=\"") << dashOffset << QLatin1String("\" ");
            break;
         }
         default:
            qWarning("QSvgPaintEngine::qpenToSvg() Unsupported pen style");
            break;
      }

      if (spen.widthF() == 0) {
         stream() << "stroke-width=\"1\" ";
      } else {
         stream() << "stroke-width=\"" << spen.widthF() << "\" ";
      }

      switch (spen.capStyle()) {
         case Qt::FlatCap:
            stream() << "stroke-linecap=\"butt\" ";
            break;
         case Qt::SquareCap:
            stream() << "stroke-linecap=\"square\" ";
            break;
         case Qt::RoundCap:
            stream() << "stroke-linecap=\"round\" ";
            break;
         default:
            qWarning("QSvgPaintEngine::qpenToSvg() Unsupported cap style");
      }

      switch (spen.joinStyle()) {
         case Qt::SvgMiterJoin:
         case Qt::MiterJoin:
            stream() << "stroke-linejoin=\"miter\" "
                     "stroke-miterlimit=\"" << spen.miterLimit() << "\" ";
            break;
         case Qt::BevelJoin:
            stream() << "stroke-linejoin=\"bevel\" ";
            break;
         case Qt::RoundJoin:
            stream() << "stroke-linejoin=\"round\" ";
            break;
         default:
            qWarning("QSvgPaintEngine::qpenToSvg() Unsupported join style");
      }
   }
   void qbrushToSvg(const QBrush &sbrush) {
      d_func()->brush = sbrush;
      switch (sbrush.style()) {
         case Qt::SolidPattern: {
            QString color, colorOpacity;
            translate_color(sbrush.color(), &color, &colorOpacity);
            stream() << "fill=\"" << color << "\" "
                     "fill-opacity=\""
                     << colorOpacity << "\" ";
            d_func()->attributes.fill = color;
            d_func()->attributes.fillOpacity = colorOpacity;
         }
         break;
         case Qt::LinearGradientPattern:
            saveLinearGradientBrush(sbrush.gradient());
            d_func()->attributes.fill = QString::fromLatin1("url(#%1)").formatArg(d_func()->currentGradientName);
            d_func()->attributes.fillOpacity = QString();
            stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
            break;
         case Qt::RadialGradientPattern:
            saveRadialGradientBrush(sbrush.gradient());
            d_func()->attributes.fill = QString::fromLatin1("url(#%1)").formatArg(d_func()->currentGradientName);
            d_func()->attributes.fillOpacity = QString();
            stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
            break;
         case Qt::ConicalGradientPattern:
            saveConicalGradientBrush(sbrush.gradient());
            d_func()->attributes.fill = QString::fromLatin1("url(#%1)").formatArg(d_func()->currentGradientName);
            d_func()->attributes.fillOpacity = QString();
            stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
            break;
         case Qt::NoBrush:
            stream() << QLatin1String("fill=\"none\" ");
            d_func()->attributes.fill = QLatin1String("none");
            d_func()->attributes.fillOpacity = QString();
            return;
            break;
         default:
            break;
      }
   }
   void qfontToSvg(const QFont &sfont) {
      Q_D(QSvgPaintEngine);

      d->font = sfont;

      if (d->font.pixelSize() == -1) {
         d->attributes.font_size = QString::number(d->font.pointSizeF() * d->resolution / 72);
      } else {
         d->attributes.font_size = QString::number(d->font.pixelSize());
      }

      int svgWeight = d->font.weight();
      switch (svgWeight) {
         case QFont::Light:
            svgWeight = 100;
            break;
         case QFont::Normal:
            svgWeight = 400;
            break;
         case QFont::Bold:
            svgWeight = 700;
            break;
         default:
            svgWeight *= 10;
      }

      d->attributes.font_weight = QString::number(svgWeight);
      d->attributes.font_family = d->font.family();
      d->attributes.font_style = d->font.italic() ? QLatin1String("italic") : QLatin1String("normal");

      *d->stream << "font-family=\"" << d->attributes.font_family << "\" "
                 "font-size=\"" << d->attributes.font_size << "\" "
                 "font-weight=\"" << d->attributes.font_weight << "\" "
                 "font-style=\"" << d->attributes.font_style << "\" "
                 << endl;
   }
};

class QSvgGeneratorPrivate
{
 public:
   QSvgPaintEngine *engine;

   uint owns_iodevice : 1;
   QString fileName;
};

QSvgGenerator::QSvgGenerator()
   : d_ptr(new QSvgGeneratorPrivate)
{
   Q_D(QSvgGenerator);

   d->engine = new QSvgPaintEngine;
   d->owns_iodevice = false;
}

QSvgGenerator::~QSvgGenerator()
{
   Q_D(QSvgGenerator);
   if (d->owns_iodevice) {
      delete d->engine->outputDevice();
   }
   delete d->engine;
}

QString QSvgGenerator::title() const
{
   Q_D(const QSvgGenerator);

   return d->engine->documentTitle();
}

void QSvgGenerator::setTitle(const QString &title)
{
   Q_D(QSvgGenerator);

   d->engine->setDocumentTitle(title);
}

QString QSvgGenerator::description() const
{
   Q_D(const QSvgGenerator);

   return d->engine->documentDescription();
}

void QSvgGenerator::setDescription(const QString &description)
{
   Q_D(QSvgGenerator);

   d->engine->setDocumentDescription(description);
}

QSize QSvgGenerator::size() const
{
   Q_D(const QSvgGenerator);
   return d->engine->size();
}

void QSvgGenerator::setSize(const QSize &size)
{
   Q_D(QSvgGenerator);
   if (d->engine->isActive()) {
      qWarning("QSvgGenerator::setSize() Unable to set size while SVG is being generated");
      return;
   }
   d->engine->setSize(size);
}

QRectF QSvgGenerator::viewBoxF() const
{
   Q_D(const QSvgGenerator);
   return d->engine->viewBox();
}

QRect QSvgGenerator::viewBox() const
{
   Q_D(const QSvgGenerator);
   return d->engine->viewBox().toRect();
}

void QSvgGenerator::setViewBox(const QRectF &viewBox)
{
   Q_D(QSvgGenerator);
   if (d->engine->isActive()) {
      qWarning("QSvgGenerator::setViewBox() Unable to set viewBox while SVG is being generated");
      return;
   }
   d->engine->setViewBox(viewBox);
}

void QSvgGenerator::setViewBox(const QRect &viewBox)
{
   setViewBox(QRectF(viewBox));
}

QString QSvgGenerator::fileName() const
{
   Q_D(const QSvgGenerator);
   return d->fileName;
}

void QSvgGenerator::setFileName(const QString &fileName)
{
   Q_D(QSvgGenerator);
   if (d->engine->isActive()) {
      qWarning("QSvgGenerator::setFileName() Unable to set file name while SVG is being generated");
      return;
   }

   if (d->owns_iodevice) {
      delete d->engine->outputDevice();
   }

   d->owns_iodevice = true;

   d->fileName = fileName;
   QFile *file = new QFile(fileName);
   d->engine->setOutputDevice(file);
}

QIODevice *QSvgGenerator::outputDevice() const
{
   Q_D(const QSvgGenerator);
   return d->engine->outputDevice();
}

void QSvgGenerator::setOutputDevice(QIODevice *outputDevice)
{
   Q_D(QSvgGenerator);
   if (d->engine->isActive()) {
      qWarning("QSvgGenerator::setOutputDevice() Unable to set output device while SVG is being generated");
      return;
   }
   d->owns_iodevice = false;
   d->engine->setOutputDevice(outputDevice);
   d->fileName = QString();
}

int QSvgGenerator::resolution() const
{
   Q_D(const QSvgGenerator);
   return d->engine->resolution();
}

void QSvgGenerator::setResolution(int dpi)
{
   Q_D(QSvgGenerator);
   d->engine->setResolution(dpi);
}

QPaintEngine *QSvgGenerator::paintEngine() const
{
   Q_D(const QSvgGenerator);
   return d->engine;
}

int QSvgGenerator::metric(QPaintDevice::PaintDeviceMetric metric) const
{
   Q_D(const QSvgGenerator);

   switch (metric) {
      case QPaintDevice::PdmDepth:
         return 32;

      case QPaintDevice::PdmWidth:
         return d->engine->size().width();

      case QPaintDevice::PdmHeight:
         return d->engine->size().height();

      case QPaintDevice::PdmDpiX:
         return d->engine->resolution();

      case QPaintDevice::PdmDpiY:
         return d->engine->resolution();

      case QPaintDevice::PdmHeightMM:
         return qRound(d->engine->size().height() * 25.4 / d->engine->resolution());

      case QPaintDevice::PdmWidthMM:
         return qRound(d->engine->size().width() * 25.4 / d->engine->resolution());

      case QPaintDevice::PdmNumColors:
         return 0xffffffff;

      case QPaintDevice::PdmPhysicalDpiX:
         return d->engine->resolution();

      case QPaintDevice::PdmPhysicalDpiY:
         return d->engine->resolution();

      case QPaintDevice::PdmDevicePixelRatio:
      case QPaintDevice::PdmDevicePixelRatioScaled:
        return 1;

      default:
         qWarning("QSvgGenerator::metric() Unhandled metric %d\n", metric);
         break;
   }
   return 0;
}

bool QSvgPaintEngine::begin(QPaintDevice *)
{
   Q_D(QSvgPaintEngine);
   if (!d->outputDevice) {
      qWarning("QSvgPaintEngine::begin() No output device");
      return false;
   }

   if (!d->outputDevice->isOpen()) {
      if (!d->outputDevice->open(QIODevice::WriteOnly | QIODevice::Text)) {
         qWarning("QSvgPaintEngine::begin() Unable to open output device, '%s'",
                  csPrintable(d->outputDevice->errorString()));
         return false;
      }

   } else if (!d->outputDevice->isWritable()) {
      qWarning("QSvgPaintEngine::begin() Unable to write to a read-only output device, '%s'",
               csPrintable(d->outputDevice->errorString()));
      return false;
   }

   d->stream = new QTextStream(&d->header);

   // stream out the header...
   *d->stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << endl << "<svg";

   if (d->size.isValid()) {
      qreal wmm = d->size.width() * 25.4 / d->resolution;
      qreal hmm = d->size.height() * 25.4 / d->resolution;
      *d->stream << " width=\"" << wmm << "mm\" height=\"" << hmm << "mm\"" << endl;
   }

   if (d->viewBox.isValid()) {
      *d->stream << " viewBox=\"" << d->viewBox.left() << ' ' << d->viewBox.top();
      *d->stream << ' ' << d->viewBox.width() << ' ' << d->viewBox.height() << '\"' << endl;
   }

   *d->stream << " xmlns=\"http://www.w3.org/2000/svg\""
              " xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
              " version=\"1.2\" baseProfile=\"tiny\">" << endl;

   if (!d->attributes.document_title.isEmpty()) {
      *d->stream << "<title>" << d->attributes.document_title << "</title>" << endl;
   }

   if (!d->attributes.document_description.isEmpty()) {
      *d->stream << "<desc>" << d->attributes.document_description << "</desc>" << endl;
   }

   d->stream->setString(&d->defs);
   *d->stream << "<defs>\n";

   d->stream->setString(&d->body);
   // Start the initial graphics state...
   *d->stream << "<g ";
   generateQtDefaults();
   *d->stream << endl;

   return true;
}

bool QSvgPaintEngine::end()
{
   Q_D(QSvgPaintEngine);

   d->stream->setString(&d->defs);
   *d->stream << "</defs>\n";

   d->stream->setDevice(d->outputDevice);
#ifndef QT_NO_TEXTCODEC
   d->stream->setCodec(QTextCodec::codecForName("UTF-8"));
#endif

   *d->stream << d->header;
   *d->stream << d->defs;
   *d->stream << d->body;
   if (d->afterFirstUpdate) {
      *d->stream << "</g>" << endl;   // close the updateState
   }

   *d->stream << "</g>" << endl // close the Qt defaults
              << "</svg>" << endl;

   delete d->stream;

   return true;
}

void QSvgPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr)
{
   drawImage(r, pm.toImage(), sr);
}

void QSvgPaintEngine::drawImage(const QRectF &r, const QImage &image,
            const QRectF &sr, Qt::ImageConversionFlags flags)
{
   //Q_D(QSvgPaintEngine);

   (void) sr;
   (void) flags;

   stream() << "<image ";
   stream() << "x=\"" << r.x() << "\" "
            "y=\"" << r.y() << "\" "
            "width=\"" << r.width() << "\" "
            "height=\"" << r.height() << "\" "
            "preserveAspectRatio=\"none\" ";

   QByteArray data;
   QBuffer buffer(&data);
   buffer.open(QBuffer::ReadWrite);
   image.save(&buffer, "PNG");
   buffer.close();
   stream() << "xlink:href=\"data:image/png;base64,"
            << data.toBase64()
            << "\" />\n";
}

void QSvgPaintEngine::updateState(const QPaintEngineState &state)
{
   Q_D(QSvgPaintEngine);
   QPaintEngine::DirtyFlags flags = state.state();

   // always stream full gstate, which is not required, but...
   flags |= QPaintEngine::AllDirty;

   // close old state and start a new one...
   if (d->afterFirstUpdate) {
      *d->stream << "</g>\n\n";
   }

   *d->stream << "<g ";

   if (flags & QPaintEngine::DirtyBrush) {
      qbrushToSvg(state.brush());
   }

   if (flags & QPaintEngine::DirtyPen) {
      qpenToSvg(state.pen());
   }

   if (flags & QPaintEngine::DirtyTransform) {
      d->matrix = state.matrix();
      *d->stream << "transform=\"matrix(" << d->matrix.m11() << ','
                 << d->matrix.m12() << ','
                 << d->matrix.m21() << ',' << d->matrix.m22() << ','
                 << d->matrix.dx() << ',' << d->matrix.dy()
                 << ")\""
                 << endl;
   }

   if (flags & QPaintEngine::DirtyFont) {
      qfontToSvg(state.font());
   }

   if (flags & QPaintEngine::DirtyOpacity) {
      if (!qFuzzyIsNull(state.opacity() - 1)) {
         stream() << "opacity=\"" << state.opacity() << "\" ";
      }
   }

   *d->stream << '>' << endl;

   d->afterFirstUpdate = true;
}

void QSvgPaintEngine::drawEllipse(const QRectF &r)
{
    Q_D(QSvgPaintEngine);

    const bool isCircle = r.width() == r.height();
    *d->stream << '<' << (isCircle ? "circle" : "ellipse");

    if (m_engineState->pen().isCosmetic()) {
        *d->stream << " vector-effect=\"non-scaling-stroke\"";
    }

    const QPointF c = r.center();
    *d->stream << " cx=\"" << c.x() << "\" cy=\"" << c.y();

    if (isCircle) {
        *d->stream << "\" r=\"" << r.width() / qreal(2.0);
    } else {
        *d->stream << "\" rx=\"" << r.width() / qreal(2.0) << "\" ry=\"" << r.height() / qreal(2.0);
    }

    *d->stream << "\"/>" << endl;
}

void QSvgPaintEngine::drawPath(const QPainterPath &p)
{
   Q_D(QSvgPaintEngine);

   *d->stream << "<path vector-effect=\""
              << (m_engineState->pen().isCosmetic() ? "non-scaling-stroke" : "none")
              << "\" fill-rule=\""
              << (p.fillRule() == Qt::OddEvenFill ? "evenodd" : "nonzero")
              << "\" d=\"";

   for (int i = 0; i < p.elementCount(); ++i) {
      const QPainterPath::Element &e = p.elementAt(i);

      switch (e.type) {
         case QPainterPath::MoveToElement:
            *d->stream << 'M' << e.x << ',' << e.y;
            break;

         case QPainterPath::LineToElement:
            *d->stream << 'L' << e.x << ',' << e.y;
            break;

         case QPainterPath::CurveToElement:
            *d->stream << 'C' << e.x << ',' << e.y;
            ++i;

            while (i < p.elementCount()) {
               const QPainterPath::Element &newElement = p.elementAt(i);

               if (newElement.type != QPainterPath::CurveToDataElement) {
                  --i;
                  break;

               } else {
                  *d->stream << ' ';
               }

               *d->stream << newElement.x << ',' << newElement.y;
               ++i;
            }

            break;

         default:
            break;
      }

      if (i != p.elementCount() - 1) {
         *d->stream << ' ';
      }
   }

   *d->stream << "\"/>" << endl;
}

void QSvgPaintEngine::drawPolygon(const QPointF *points, int pointCount,
                                  PolygonDrawMode mode)
{
   Q_ASSERT(pointCount >= 2);

   //Q_D(QSvgPaintEngine);

   QPainterPath path(points[0]);
   for (int i = 1; i < pointCount; ++i) {
      path.lineTo(points[i]);
   }

   if (mode == PolylineMode) {
      stream() << "<polyline fill=\"none\" vector-effect=\""
               << (m_engineState->pen().isCosmetic() ? "non-scaling-stroke" : "none")
               << "\" points=\"";

      for (int i = 0; i < pointCount; ++i) {
         const QPointF &pt = points[i];
         stream() << pt.x() << ',' << pt.y() << ' ';
      }

      stream() << "\" />" << endl;

   } else {
      path.closeSubpath();
      drawPath(path);
   }
}

void QSvgPaintEngine::drawRects(const QRectF *rects, int rectCount)
{
    Q_D(QSvgPaintEngine);
    for (int i=0; i < rectCount; ++i) {
        const QRectF &rect = rects[i].normalized();

        *d->stream << "<rect";

        if (m_engineState->pen().isCosmetic()) {
            *d->stream << " vector-effect=\"non-scaling-stroke\"";
        }

        *d->stream << " x=\"" << rect.x() << "\" y=\"" << rect.y()
                   << "\" width=\"" << rect.width() << "\" height=\"" << rect.height()
                   << "\"/>" << endl;
    }
}
void QSvgPaintEngine::drawTextItem(const QPointF &pt, const QTextItem &textItem)
{
   Q_D(QSvgPaintEngine);

   if (d->pen.style() == Qt::NoPen) {
      return;
   }

   const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);

   if (ti.text().isEmpty()) {
      QPaintEngine::drawTextItem(pt, ti); // Draw as path
   }

   QString s = QString(ti.m_iter, ti.m_end);

   *d->stream << "<text "
              "fill=\"" << d->attributes.stroke << "\" "
              "fill-opacity=\"" << d->attributes.strokeOpacity << "\" "
              "stroke=\"none\" "
              "xml:space=\"preserve\" "
              "x=\"" << pt.x() << "\" y=\"" << pt.y() << "\" ";

   qfontToSvg(textItem.font());
   *d->stream << " >"
              << s.toHtmlEscaped()
              << "</text>"
              << endl;
}


#endif // QT_NO_SVGGENERATOR
